The problem that I am experiencing is that the app keeps working if you try to cancel the subscription.
Here is my code snippet:
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "Seb.DiPlus.RenewingSubMonthAuto", "Seb.DiPlus.RenewingSubYearAuto")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
#IBAction func subscribeMonth(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "Seb.DiPlus.RenewingSubMonthAuto") {
p = product
buyProduct()
}
}
}
#IBAction func subscribeYear(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "Seb.DiPlus.RenewingSubYearAuto") {
p = product
buyProduct()
}
}
}
#IBAction func restoreComplete(_ sender: UIButton) {
restorePurchases()
}
#IBAction func exit(_ sender: Any) {
_exit(0)
}
func restorePurchases() {
if SKPaymentQueue.canMakePayments(){
print("restored complete")
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
else{
print("restored faild, IAP not activ?")
}
}
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
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)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "Seb.DiPlus.RenewingSubMonthAuto":
print("Subscribe Month!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
case "Seb.DiPlus.RenewingSubYearAuto":
print("Subscribe Year!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
default:
print("IAP not found")
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print("ERROR: ", trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "Seb.DiPlus.RenewingSubMonthAuto":
print("Subscribe Month!!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
case "Seb.DiPlus.RenewingSubYearAuto":
print("Subscribe Year!!")
if abo < 1 {
readySubscribe()
}
abo = 1
break
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
alert(title: "ERROR", message: trans.error?.localizedDescription)
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
func readySubscribe() {
UserDefaults.standard.setValue(checkSubscribe, forKeyPath: "subscribe")
UserDefaults.standard.synchronize()
self.performSegue(withIdentifier: "readySubscribe", sender: self)
}
func alert (title:String, message:String?){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
Once I click on restore purchases, you can continue to use the app even though the subscription is no longer activated.
So the function readySubscribe() is called.
You should not activate any purchases in paymentQueueRestoreCompletedTransactionsFinished. This function is called when the restoration process is complete. You can use it to update your UI or alert the user.
The actual restoration of products should be handled in your paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) function.
Restored transactions appear with a state of .restored. You should process them exactly as you process a .purchased state.
Since you are using auto-renewing subscription IAPs you also need to check expiration dates from the receipt and be prepared for new transactions to be presented when the subscription renews. For this reason one of the first things your app should do in didFinishLaunchingWithOptions is create a payment queue observer.
Related
I am playing for couple days already with in-app purchases and found next issue (let me know if it expected behavior).
I have application on TestFlight at the moment, not in Apple Store.
I have a list of items that user may purchase and when user taps "Purchase" it goes through all normal steps: product is selected, then I show my AlertViewController with confirmation of purchase, and on completion of "submit" action of AlertViewController I call buyProduct in my IAP Manager . Then normal dialog about "Cancel / Buy" appears on dialog "Confirm Your In-App Purchase" with [Environment: Sandbox] etc.
However when I tap "Buy" I am redirected to the previous View Controller and "You're all set." is displayed on the previous View Controller, not the one I was at.
If it is OK for TestFlight version - then fine. If this is the behavior I will have at Apple Store then it is bad I need a fix. Here is my in-app purchase manager:
class IAPManager: NSObject {
static let sharedInstance = IAPManager()
static let IAPManagerPurchaseNotification = "IAPManagerPurchaseNotification"
fileprivate var productsRequest: SKProductsRequest?
fileprivate var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
func initialize() {
SKPaymentQueue.default().add(self)
}
func deinitialize() {
SKPaymentQueue.default().remove(self)
}
/// Request products from Apple Store.
/// - parameter products: IAPProduct to purchase.
func requestProduct(products: [IAPProduct], _ completionHandler: #escaping ProductsRequestCompletionHandler) {
productsRequest?.cancel()
productsRequestCompletionHandler = completionHandler
var productIdentifiers: Set<String> = Set<String>()
for product in products {
productIdentifiers.insert(product.rawValue)
}
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest?.delegate = self
productsRequest?.start()
}
/// Does actual purchasing against Apple Store.
/// - parameter product: product that user wants to purchase.
func buyProduct(_ product: SKProduct) {
Log.info(message: "User \(UsersManager.sharedInstance.currentUserId) is purchasing \(product.productIdentifier)", sender: self)
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
class func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
/// Restores previously purchased non-consumable items.
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
extension IAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
Log.info(message: "Loaded list of products...", sender: self)
let products = response.products
productsRequestCompletionHandler?(true, products)
clearRequestAndHandler()
for p in products {
Log.info(message: "Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)", sender: self)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
Log.error(message: "Failed to load list of products with error: \(error.localizedDescription)", sender: self)
productsRequestCompletionHandler?(false, nil)
clearRequestAndHandler()
}
private func clearRequestAndHandler() {
productsRequest = nil
productsRequestCompletionHandler = nil
}
}
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased: complete(transaction: transaction)
case .failed: fail(transaction: transaction)
case .restored: restore(transaction: transaction)
case .deferred: Log.info(message: "Purchase is deffered", sender: self)
case .purchasing: Log.info(message: "Purchase is in progress", sender: self)
}
}
}
private func complete(transaction: SKPaymentTransaction) {
Log.info(message: "Purchase is completed.", sender: self)
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else {
return
}
Log.info(message: "Purchase \(productIdentifier) is in restore state.", sender: self)
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
Log.info(message: "Purchase is failed.", sender: self)
if let transactionError = transaction.error as NSError? {
if transactionError.code != SKError.paymentCancelled.rawValue {
Log.error(message: "Transaction Error: \(transactionError.localizedDescription)", sender: self)
}
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else {
return
}
NotificationCenter.default.post(name: NSNotification.Name(rawValue: IAPManager.IAPManagerPurchaseNotification), object: identifier)
}
}
Code that calls IAPManager:
let alert = UIAlertController(title: "<Title>", message: "<Message>", preferredStyle: .alert)
IAPManager.sharedInstance.requestProduct(products: [product]) { success, products in
guard success, let products = products, !products.isEmpty else {
Log.error(message: "Error occured during product request.", sender: self)
return
}
let product = products[0]
let submit = UIAlertAction(title: "Ok", style: .default) { action in
IAPManager.sharedInstance.buyProduct(product)
}
let cancel = UIAlertAction(title: "No", style: .destructive, handler: nil)
alert.addAction(submit)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
Issue disappeared on its own. I was fixing multithreading and probably that's was the cause. I cannot reproduce right now, but if I will find the real cause - I'll post it here. Thanks all.
I have implemented an IAP Scene OptionsPage: SKScene where users can purchase "Remove Ads" and in game currency etc. But if I go from MenuScene: SKScene to the OptionsPage: SKScene and back to the menu quickly it crashes with Thread 1: EXC_BAD_ACCESS (code=1, address=0x216d0716) and the only thing in the log down the bottom is (lldb).
I am calling SKPaymentQueue.defaultQueue().removeTransactionObserver(self) in willMoveFromView().
In my didMoveToView I call the setIAP() function (shown below).
After constantly bashing my head against a table (first time dealing with IAP) , I think that it is happening because I've set request.delegate = self and so when I enter the scene request.start() runs but because I quickly leave the scene request.delegate = self is no longer valid.
I thought removing the transaction observer would deal with this in willMoveFromView. How can I get around this problem?
Here is how I am calling the purchase in touchesBegan()
if removeAds.containsPoint(location) {
for product in list {
let prodID = product.productIdentifier
if (prodID == "removeAds") {
p = product
buyProduct()
break
}
}
}
Here is how the IAP section looks in
// MARK: In App Purchases
func setIAP() {
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID:NSSet = NSSet(objects: "removeAds, 10000coins")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
var list = [SKProduct]()
var p = SKProduct()
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("Product added: \(product.productIdentifier), \(product.localizedTitle), \(product.localizedDescription), \(product.price)")
list.append(product)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "removeAds":
defaults.setBool(true, forKey: "removeAdsPurchased")
default:
print("IAP not setup")
}
}
let alert = UIAlertController(title: "Thank You", message: "Thankyou for your purchase.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add paymnet")
for transaction:AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .Purchased, .Restored:
print(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "removeAds":
defaults.setBool(true, forKey: "removeAdsPurchased")
case "10000Coins":
defaults.setInteger(bank + 10000, forKey: "bankValue")
bank = defaults.integerForKey("bankValue")
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
print("buy error")
queue.finishTransaction(trans)
break;
default:
print("default")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction) {
print("Transaction finished")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Transaction removed")
}
The way I solved this, seeing as there was no feedback, was very tedious but as follows:
Rather than have two scenes I combined the Menu and options scene so that when the options button is tapped, it runs an action to slide the menu nodes off the screen and options onto the screen. This way the request.delegate = self doesn't change therefore avoiding the crash. Then the only issue was if I launch the app and press the play button quickly to move to the game scene before the request.start finishes it would get the same crash. So I created a loading splash screen to run for 2 seconds in the menu scenes didMoveToView this way it had plenty of time to get all the data before allowing interaction.
I would love to hear of a more simple solution but with the little experience I have this is what I came up with.
I'm creating an Application in Swift with a list of in-app purchase. What I'm trying to do is to obtain/retrieve a list of "Already Purchased" Product Identifiers.
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
}
For Example, in the above method we can get a list of available products. Instead of getting that list, I want to obtain the list of Already Purchased Product IDs. Then I would loop through that list and enable all the features for those products.
Is there any way to obtain that list? Or some other ways you would get that list?
Too little information in your question and about your code. I suggest you check my question which I answered a while ago: My IAP isn't working. Bugs at func Paymentqueue
It contains working IAP code and a few tips. I hope that helps you !
Here is the full IAP code :
import UIKit
import StoreKit
class GameViewController: UIViewController, ADBannerViewDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver, GKGameCenterControllerDelegate,GADBannerViewDelegate{
#IBOutlet var outRemoveAds: UIButton!
#IBOutlet var outRestorePurchases: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("val") != nil {
print("Has a value.")
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
removeInterFrom = 1
}
else {
print("No Value.")
}
if(SKPaymentQueue.canMakePayments()){
print("IAP is enabled, loading...")
let productID:NSSet = NSSet(objects:"IAP id")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
else{
print("Please enable IAPS")
}
}
//IAP Ads
#IBAction func removeAds(sender: UIButton) {
for product in list{
let prodID = product.productIdentifier
if (prodID == "IAP id"){
p = product
buyProduct()
break
}
}
}
#IBAction func restorePurchases(sender: UIButton) {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
//IAP Functions
var list = [SKProduct]()
var p = SKProduct()
func removeAds(){
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
let theValue = 10
NSUserDefaults.standardUserDefaults().setObject(theValue, forKey: "val")
NSUserDefaults.standardUserDefaults().synchronize()
}
func buyProduct(){
print("Buy: "+p.productIdentifier)
let pay = SKPayment (product: p)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest, didReceiveResponse 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)
}
}
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("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("Purchases Restored")
_ = []
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID{
case "IAP id":
print("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
}
}
func finishTransaction(trans:SKPaymentTransaction){
print("Finshed Transaction")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Remove Transaction")
}
}
Put:
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
In viewDidLoad or viewDidAppear.
I am using IAPHelper to implement in-app purchase, an error is occurring when I go to another view controller after pressing the purchase button. For instance, when I press the purchase button then go to another view controller the error is presented after finishing the purchase work.
class selectQuestion_ViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource{
let helper = IAPHelper(productIdentifiers: NSSet(object: "sppid") as Set<NSObject>)
func purchase(sender: AnyObject)
{
startLoading("Purchasing ..")
self.helper.requestProductsWithCompletionHandler({ (success, products) -> Void in
if success {
self.endLoading()
println("wohooooo")
var sdad = self.helper.productsDict["sppid"]
self.helper.buyProduct(sdad!)
} else {
self.endLoading()
let alert = UIAlertController(title: "Error", message: "Cannot retrieve products list right now.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
}
I recommend you do not use such modules. It would be much better for you and your understanding of IAPs that you check this tutorial and implement the code and methods described there: https://www.youtube.com/watch?v=h1gQklbrgjc
Once you have implemented that code, if something doesn't work (you may have forgotten a line somewhere), come back to SO and we will gladly help you out. Already, as quick help, here is an answer that could help you with most errors once you implement your IAP code with something else than IAP helper: My IAP isn't working. Bugs at func Paymentqueue
In fact, you can watch the video and copy paste the code of my question. My IAP code works ;) Will save you time. But watch the video to understand how to implement IAPs !
I hope this answer helped you ;)
Here is the full IAP code :
import UIKit
import StoreKit
class GameViewController: UIViewController, ADBannerViewDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver, GKGameCenterControllerDelegate,GADBannerViewDelegate{
#IBOutlet var outRemoveAds: UIButton!
#IBOutlet var outRestorePurchases: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("val") != nil {
print("Has a value.")
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
removeInterFrom = 1
}
else {
print("No Value.")
}
if(SKPaymentQueue.canMakePayments()){
print("IAP is enabled, loading...")
let productID:NSSet = NSSet(objects:"IAP id")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
else{
print("Please enable IAPS")
}
}
//IAP Ads
#IBAction func removeAds(sender: UIButton) {
for product in list{
let prodID = product.productIdentifier
if (prodID == "IAP id"){
p = product
buyProduct()
break
}
}
}
#IBAction func restorePurchases(sender: UIButton) {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
//IAP Functions
var list = [SKProduct]()
var p = SKProduct()
func removeAds(){
banner.removeFromSuperview()
bannerGoogle.removeFromSuperview()
outRemoveAds.removeFromSuperview()
outRestorePurchases.removeFromSuperview()
let theValue = 10
NSUserDefaults.standardUserDefaults().setObject(theValue, forKey: "val")
NSUserDefaults.standardUserDefaults().synchronize()
}
func buyProduct(){
print("Buy: "+p.productIdentifier)
let pay = SKPayment (product: p)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
func productsRequest(request: SKProductsRequest, didReceiveResponse 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)
}
}
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("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
queue.finishTransaction(trans)
break
case .Failed:
print ("Buy error")
queue.finishTransaction(trans)
break
default:
print("default: Error")
break
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("Purchases Restored")
_ = []
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction as SKPaymentTransaction
let prodID = t.payment.productIdentifier as String
switch prodID{
case "IAP id":
print("Remove Ads")
removeAds()
default:
print("IAP not setup")
}
}
}
func finishTransaction(trans:SKPaymentTransaction){
print("Finshed Transaction")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Remove Transaction")
}
}
Put:
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
in viewDidLoad or viewDidAppear
I wrote some code to get my In-app purchase to get rid on my ads. The response comes back blank, and comes back with error because it is nil here is my full code for In-App Purchase:
override func viewDidLoad() {
super.viewDidLoad()
outRemoveAds.enabled = false
if(SKPaymentQueue.canMakePayments()) {
println("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: "com.webstart.tomsgamesinc.StartLight_No_Ads")
var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as Set<NSObject>)
request.delegate = self
request.start()
} else {
println("please enable IAPS")
}
// Do any additional setup after loading the view.
}
func buyProduct() {
println("buy " + p.productIdentifier)
var pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}
//3
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
println("product request")
println(response.products)
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)
}
outRemoveAds.enabled = true
}
// 4
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 "com.webstart.tomsgamesinc.StartLight_No_Ads":
println("remove ads")
removeAds()
default:
println("IAP not setup")
}
}
}
// 5
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:
println("buy, ok unlock iap here")
println(p.productIdentifier)
let prodID = p.productIdentifier as String
switch prodID {
case "com.webstart.tomsgamesinc.StartLight_No_Ads":
println("remove ads")
removeAds()
println("add coins to account")
default:
println("IAP not setup")
}
queue.finishTransaction(trans)
break;
case .Failed:
println("buy error")
queue.finishTransaction(trans)
break;
default:
println("default")
break;
}
}
}
// 6
func finishTransaction(trans:SKPaymentTransaction)
{
println("finish trans")
SKPaymentQueue.defaultQueue().finishTransaction(trans)
}
//7
func paymentQueue(queue: SKPaymentQueue!, removedTransactions transactions: [AnyObject]!)
{
println("remove trans");
}
#IBAction func btnRemoveAds(sender: UIButton) {
for product in list {
println("ProdID")
var prodID = product.productIdentifier
println(prodID)
if(prodID == "com.webstart.tomsgamesinc.StartLight_No_Ads") {
p = product
buyProduct()
break;
}
}
}
I did add the purchase in iTunes connect.
I have updated your code like this:
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
var productID = "com.webstart.tomsgamesinc.StartLight_No_Ads"
var product: SKProduct?
var isProductRecive = false
override func viewDidLoad() {
super.viewDidLoad()
getProductInfo()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
#IBAction func btnRemoveAds(sender: UIButton) {
if isProductRecive{
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}else{
let alert = UIAlertView()
alert.title = "Alert"
alert.message = "Retriving product please wait."
alert.addButtonWithTitle("Ok")
alert.show()
getProductInfo()
}
}
func getProductInfo()
{
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: self.productID) as Set<NSObject>)
request.delegate = self
request.start()
} else {
let alert = UIAlertView()
alert.title = "Alert"
alert.message = "Please enable In App Purchase in Settings."
alert.addButtonWithTitle("Ok")
alert.show()
}
}
//3
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
var products = response.products
if (products.count != 0) {
product = products[0] as? SKProduct
self.isProductRecive = true
println(product)
} else {
let alert = UIAlertView()
alert.title = "Alert"
alert.message = "Product not found"
alert.addButtonWithTitle("Ok")
alert.show()
}
products = response.invalidProductIdentifiers
for product in products
{
println("Product not found: \(product)")
}
}
// 4
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 "com.webstart.tomsgamesinc.StartLight_No_Ads":
println("remove ads")
default:
println("IAP not setup")
}
}
}
// 5
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch transaction.transactionState {
case .Restored:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
case .Purchased:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
default:
break
}
}
}
func request(request: SKRequest!, didFailWithError error: NSError!) {
println("There was an error");
}
}
And it is working fine with my app bundle ID an product ID so kindly check into iTunes connect problem should be there.
This code is working for my game and returns back how many products there are to purchase. Works for xCode 7.1.1.
var products = [SKProduct]()
var product: SKProduct?
//StoreKit protocol method. Called when the AppStore responds
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
var item = response.products
if item.count != 0 {
for var i = 0; i < item.count; i++ {
product = item[i] as SKProduct
products.append(product!)
}
}
print("There are \(products.count) items to buy.")
}