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?
Related
I created an In-App Purchase feature for the app, using an Auto-Renewing Subscription. The problem is that I'm not sure how to check the purchase status, whether the subscription has run out or is still active.
Here's where I try saving the purchased status:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.test.UnlockTools.Subscription1":
print("tool set 1 unlocked")
uTool1()
print("tool set 2 unlocked")
uTool2()
print("tool set 3 unlocked")
uTool3()
UserDefaults.standard.set(true, forKey: "isSubbed")
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
This is where I call the UserDefaults and allow or deny button interaction:
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(object: "com.test.UnlockTools.Subscription1")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
status = UserDefaults.standard.bool(forKey: "isSubbed") ?? false
if status == true {
unlockTool1.isUserInteractionEnabled = true
unlockTool2.isUserInteractionEnabled = true
unlockTool3.isUserInteractionEnabled = true
} else {
unlockTool1.isUserInteractionEnabled = false
unlockTool2.isUserInteractionEnabled = false
unlockTool3.isUserInteractionEnabled = false
}
}
If I find out the status then I will be able to save the state as true/false. I tried using UserDefaults with some success. Not sure if I placed the code in the best location though.
If the subscription is still active, I'd like to allow the user to click a button, and deny the button click if the subscription has run out.
I'll add more code if needed. Any help would be greatly appreciated.
The only way to check the subscription status is to verify the receipt with Apple to see if it's still valid using their /verifyReceipt endpoint - docs.
What you could do is cache some expiration date after the purchase and use that to check if the subscription is valid. If it's passed the expiration date you can re-check the receipt with Apple to see if it's renewed. There are also edge cases where a user is refunded and their subscription is cancelled before the expiration date - you should update your receipts periodically with Apple to check this case. Ideally, this should all be happening server side as well to avoid piracy.
Here's a great post that summarizes the nauces of Apple subscriptions very well: iOS Subscriptions are Hard
I am developing IAP functionality of non consumable (removing Ads).
I've created an helper for all the operation and everything works fine.
When a user buy/restore the purchase i set this:
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
This works fine as long as the user never delete the app.
I was wondering...
Is there a way to know if the user (after deleting and reinstalling the app) already had made a purchase? so to change the title of the button from "purchase" to "restore"?
For every app, Apple requires that you include a "Restore Purchases" button. This is exactly for the problem you are facing. It will recreate the SKPaymentTransaction for every previously purchased IAP with a "restored" state for the iCloud account that is currently signed in at the App Store, and put on the SKPaymentQueue. Read more...
Maybe it would make sense to have a receipt validation in your code, when the user is interacting in your app. (Apple docs: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Introduction.html)
Just have a look at this small example:
Init your product requests:
if SKPaymentQueue.canMakePayments() {
print("starting IAPS")
let productIdentifiers = Set(["YOUR_IAPP_IDENTIFIER#1", "YOUR_IAPP_IDENTIFIER#1"])
let request = SKProductsRequest(productIdentifiers: productIdentifiers as Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
Set your products in your app and do the receipt validation
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
if product.productIdentifier == "YOUR_IAPP_IDENTIFIER#1" {
self.productYear = product
} else if product.productIdentifier == "YOUR_IAPP_IDENTIFIER#2" {
self.productMonth = product
}
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
self.receiptValidation()
}
Validation example
func receiptValidation() {
self.logger.log.debug("In receipt validation")
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
let dict = ["receipt-data" : receiptString, "password" : "YOUR_PASSWORD"] as [String : Any]
// Getting the receipt data from the JSON file and other logic
// ...
// ...
}
}
}
Please let me know, if you need more clarification on this. Furthermore you can simply restore it with the PaymentQueue (Apple Doc: https://developer.apple.com/documentation/storekit/skpaymentqueue)
If you provide a login system you will know easily , otherwise
no other way you should put buy and restore buttons for payment Or you store this key-value in
device key-chain and read them in first setup ,but you should know from ios 10.3 If you deleted the app the associated key-chain items will be deleted
Note: Apple states that all apps that have IAP should put a restore payment functionality so, If there is a workaround app will be rejected by Apple
I have done every step which apple says. But still cannot get products. This part looks ok. I can see 'IAP is enabled. loading' message.
if SKPaymentQueue.canMakePayments() {
print("IAP is enabled. loading")
let productID:NSSet = NSSet(objects: "com.companyname.appname.onecredit", "com.companyname.appname.threecredits")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
And this is the SKProductsRequest of the code. Which I cannot get any product.
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product as SKProduct)
}
}
The only thing, on the Agreements, Tax, and Banking section, status is still 'pending tax'. Is this the reason?
The problem was US Tax Form. Once its approved, everything is fine.
Yes - I can confirm that ALL of the "Agreements, Tax, and Banking" in iTunes Connect need to be "COMPLETED" in full. After I filled out all the info, it took about 30 minutes for the PROCESSING status to be finished. Then I was able to get a response from the SKProductsRequestDelegate for my list of products! Yay!
I have completed a small app where I have a non-consumable purchase option. It is on the App Store.
The purchase of the product runs OK. It's my Restore Purchase function that seems to do nothing.
I have added this code for the Restore Purchase #IBAction:
#IBAction func restorePurchases(sender: AnyObject) {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
But nothing happens when I hit the restore purchase button.
I think I have to add a function that checks if the restore was successful or not. Am planning to amend code to the following:
#IBAction func restorePurchases(sender: AnyObject) {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Restored:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as SKPaymentTransaction)
var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
break;
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as SKPaymentTransaction)
var alert = UIAlertView(title: "Sorry", message: "Your purchase(s) could not be restored.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
break;
default:
break;
}
}
}
Will this do the trick?
I have been through every thread in relation to effecting Restore Purchase transactions, and my research has led me to the above. So I don't think this is a duplicate of a question, but perhaps may clarify how to successfully restore purchases for others facing my similar situation.
Your codes looks pretty fine for the most part, although some parts seem to be from older tutorials . There is some changes you should make, one of them is that you need to call your unlockProduct function again.
This is the code I use (Swift 3).
/// Updated transactions
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
// Transaction is being added to the server queue.
case .purchased:
// Transaction is in queue, user has been charged. Client should complete the transaction.
defer {
queue.finishTransaction(transaction)
}
let productIdentifier = transaction.payment.productIdentifier
unlockProduct(withIdentifier: productIdentifier)
case .failed:
// Transaction was cancelled or failed before being added to the server queue.
defer {
queue.finishTransaction(transaction)
}
let errorCode = (transaction.error as? SKError)?.code
if errorCode == .paymentCancelled {
print("Transaction failed - user cancelled payment")
} else if errorCode == .paymentNotAllowed { // Will show alert automatically
print("Transaction failed - payments are not allowed")
} else {
print("Transaction failed - other error")
// Show alert with localised error description
}
case .restored:
// Transaction was restored from user's purchase history. Client should complete the transaction.
defer {
queue.finishTransaction(transaction)
}
if let productIdentifier = transaction.original?.payment.productIdentifier {
unlockProduct(withIdentifier: productIdentifier)
}
case .deferred:
// The transaction is in the queue, but its final status is pending external action
// e.g family member approval (FamilySharing).
// DO NOT freeze up app. Treate as if transaction has not started yet.
}
}
}
Than use the delegate methods to show the restore alert
/// Restore finished
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
guard queue.transactions.count != 0 else {
// showAlert that nothing restored
return
}
// show restore successful alert
}
/// Restore failed
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {
/// handle the restore error if you need to.
}
Unlock product is just a method I am sure you already have too.
func unlockProduct(withIdentifier productIdentifier: String) {
switch productIdentifier {
/// unlock product for correct ID
}
}
As a side note, you should move this line
SKPaymentQueue.default().add(self)
out of your restore and buy function and put it in viewDidLoad.
Apple recommends you add the transaction observer as soon as your app launches and only remove it when your app is closed. A lot of tutorials unfortunately dont teach you this correctly. This way you unsure that any incomplete transactions e.g due to network error, will always resume correctly.
https://developer.apple.com/library/content/technotes/tn2387/_index.html
In my real projects my code for IAPs is in a Singleton class so I would actually using delegation to forward the unlockProduct method to my class that handles gameData. I can than also make sure the observer is added at app launch.
Hope this helps
Took me a while to suss out, but the reason my StoreKit was not updatingTransactions and restoring the purchase was because of a broken Configuration setting in my app's Scheme. When I set that to None, it worked!
In Xcode I went into Edit>Scheme (image1) clicked on the Run>Options tab and selected None for StoreKit Configuration (image2). I also went on my physical device, and logged out of my personal Apple purchase account (Settings >Your Name/Pic at the Top > Media & Purchases > Sign Out) (image3). And finally, this step might not be critical, but I logged into my test sandbox account on my device at the bottom of the Settings>App Store menu(image4 and image5). That is an account that I setup in developer.apple.com under test users.
Ok, so I'm working in Swift and I just need help. I have followed 4 different tutorials on how to implement in app purchases in sprite kit with Swift, copied code verbatim, and nothing is working for me.
Here are the steps I have taken:
Gone in Itunes Connect and created an in app purchase under my app's record. My in app purchase's product Id is "GameOverSaveSavior"
In Xcode, I've turned my app's In app purchase capability to ON, made sure my team is set to my account, and made sure my Bundle Identifier under info is set to my app's bundle identifier in ITunes Connect
Before writing any code, I have import StoreKit in my GameScene.swift file
As for code, this is what I have done:
(1) In Gamescene.swift, at the end of my didMoveToView func, I have:
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "GameOverSaveSavior")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
This outputs "IAP is enabled, loading" when the app is run.
(2) In GameScene.swift, within the class but outside of didMoveToView, I have all the functions and variables others have used for in app purchases:
var list = [SKProduct]()
var p = SKProduct()
func purchaseMade() {
println("they bought it!")
}
func buyProduct() {
println("buy" + p.productIdentifier)
var pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
println("product request")
var myProduct = response.products
for product in myProduct {
println("product added")
println(product.productIdentifier)
println(product.localizedTitle)
println(product.localizedDescription)
println(product.price)
list.append(product as! SKProduct)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
println("transactions restored")
var purchasedItemIDS = []
for transaction in queue.transactions {
var t: SKPaymentTransaction = transaction as! SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
purchaseMade()
//Right here is where you should put the function that you want to execute when your in app purchase is complete
default:
println("IAP not setup")
}
}
var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored. You may have to restart the app before banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
println("add paymnet")
for transaction:AnyObject in transactions {
var trans = transaction as! SKPaymentTransaction
println(trans.error)
switch trans.transactionState {
case .Purchased, .Restored:
println("buy, ok unlock iap here")
println(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "GameOverSaveSavior":
//Here you should put the function you want to execute when the purchase is complete
var alert = UIAlertView(title: "Thank You", message: "You may have to restart the app before the banner ads are removed.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
default:
println("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
println("buy error")
queue.finishTransaction(trans)
break;
default:
println("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction)
{
println("finish trans")
}
func paymentQueue(queue: SKPaymentQueue!, removedTransactions transactions: [AnyObject]!)
{
println("remove trans");
}
This outputs "product request" to the console when the app is run.
(3) In GameScene.swift, in my touchesBegan func, I have the following for when the right button is touched:
//In app purchase
if touchedNode == saveMeBtn {
println("button touched!")
for product in list {
var prodID = product.productIdentifier
if(prodID == "GameOverSaveSavior") {
p = product
buyProduct() //This is one of the functions we added earlier
break;
}
}
This outputs "button touched!" when the button is touched.
I do not know what I am doing wrong. The code compiles with no errors, yet nothing occurs when my button is touched. There is no alert asking the user to sign in or to purchase anything- nothing.
I have mainly followed this question:
in app purchase in SKScene
Is there anything else I should have done prior to writing the code? Am I missing something in my code?
I think I can help you:
I had your issue in the past. And another similar one which I fixed here: My IAP isn't working. Bugs at func Paymentqueue
Here is the solution I had found:
Delete
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
everywhere you have it and put it once (ONLY ONCE) in a place where it will be executed each time your app boots up ( I put it in viewDidLoad()).
This will check for all unfinished transactions and terminate them once the app has loaded, thus removing any possible errors before your users triggers an IAP.
(If this answer helped you, don't forget to upvote ;))
P.S.: Also, this wasn't my issue, but make sure to finishTransaction() for each PurchaseState, like here:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Add Payment")
for transaction:AnyObject in transactions{
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState{
case .Purchased:
print("IAP unlocked")
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID{
case "IAP id":
print("Keep on")
keepOn()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
Never forget this:
queue.finishTransaction(trans)
And make separate case for .Restored.