Setting Up In App Purchase

Hey everyone, so iv been creating a game and the game is finally finished, i have added iAds and have made a button that allows users to remove the adds for 1.29, i have done the devaloper portal side of things and have watched many a tutorials on how to add the in app purchase functionality. I want to charge the user and then run the function removeAdvertisements which stores a proUser variable in NSUserdefault as true, then when i add the ads it checks if proUser is equal to true and then hides the add. Anyway the problem i am having is when i press the remove ads button it does not work. Any and all help will be helpful!!!!!!! BTW IM USING SWIFT 2 AND IOS 9



import Foundation

import SpriteKit

import AVFoundation

import StoreKit

class OptionsScene: SKScene, SKProductsRequestDelegate, SKPaymentTransactionObserver {



let removeAdsText = SKLabelNode(fontNamed: "Bebas Neue Bold")

let removeAds = SKSpriteNode(imageNamed: "resetHighscoreBtn")

let removeAdsComplete = SKLabelNode(fontNamed: "Bebas Neue Bold")


var backgroundMusic = AVAudioPlayer()


override func didMoveToView(view: SKView) {

removeAdsText.text = "Remove Ads"

removeAdsText.fontColor = UIColor.whiteColor()

removeAdsText.fontSize = 60

removeAdsText.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.55)

removeAdsText.zPosition = 100

self.addChild(removeAdsText)

removeAds.size = CGSize(width: 300, height: 80)

removeAds.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.45)

self.addChild(removeAds)

removeAdsComplete.text = "Thank you ads removed. Restart App"

removeAdsComplete.fontColor = UIColor.whiteColor()

removeAdsComplete.fontSize = 20

removeAdsComplete.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.35)

removeAdsComplete.hidden = true

self.addChild(removeAdsComplete)

if(SKPaymentQueue.canMakePayments()) {

print("IAP is enabled, loading")

var productID: NSSet = NSSet(objects: "com.test.example")

var request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)

request.delegate = self

request.start()

} else {

print("Device IAP Gone")

}

}


var list = [SKProduct]()

var p = SKProduct()


func buyProduct() {

print("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) {

print("Request the IAPS Products")

var 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)

}

print("Remove ads now avaliable")

}


func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

print("Add Payment")

for transaction: AnyObject in transactions {

var trans = transaction as! SKPaymentTransaction

print(trans.error)

switch trans.transactionState {

case .Purchased:

print("bought, unlock ads")

print(p.productIdentifier)

let prodID = p.productIdentifier as String

switch prodID {

case "com.test.example":

removeAdvertisements()

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("Finish Trans")

}


func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {

print("Remove Trans")

}


func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {

print("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.test.example":

print("Remove Ads")

default:

print("IAP Not setup")

}

}

}


func removeAdvertisements() {

let proUser = NSUserDefaults.standardUserDefaults()

proUser.setBool(true, forKey: "proUser")

proUser.synchronize()

removeAdsComplete.hidden = false

}



override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

for touch in touches {

let location = touch.locationInNode(self)

if self.nodeAtPoint(location) == resetHightscore {

GameScene().resetHighscore()

resetComplete.hidden = false

}

if self.nodeAtPoint(location) == removeAds {

print("print")

for product in list {

var prodID = product.productIdentifier

print(product.productIdentifier)

if(prodID == "com.test.example") {

p = product

buyProduct()

break;

}

}

}

if self.nodeAtPoint(location) == backBtn {

backgroundMusic.stop()

let goMenuScene = MenuScene(size: self.size)

goMenuScene.scaleMode = scaleMode

let transition = SKTransition.fadeWithDuration(1)

self.view?.presentScene(goMenuScene, transition: transition)

}

}

}


}

Accepted Reply

So i fixed it, i dont know how i just set break points at different places and ran it and i worked without a hitch, must have been an xcode 7 problem.. I hope 😕

Replies

So i fixed it, i dont know how i just set break points at different places and ran it and i worked without a hitch, must have been an xcode 7 problem.. I hope 😕

In reviewing your code, there is one important StoreKit programming misuse that can lead to potentially confusing problems after the fact - that is to call addTransactionObserver in a serial fashion when making a payment. The

SKPaymentQueue.defaultQueue().addTransactionObserver(self)

should only be called once during the program use.

(In your case this is the likely scenario, but's the implementation here might give another programmer the thought that this is the correct use - that the call can be made multiple time whenever processing a payment. )

The addTransactionObserver call is best made from the didFinishLaunchingWithOptions delegate method as described in

Tech Note 2387 - “Best Practices for in app purchases

https://developer.apple.com/library/ios/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

Rich,


Thank you so much for participating in this forum. Your input is much appreciated.


But....regarding always having a transaction observer, may I respectfully disagree? Adding StoreKit to a launching app if not needed (i.e. 99.9% of the time) is poor form. The user can be given the option of adding it when they request a purchase or expect to have been awarded a purchase. And, with all due respect, one never knows when or if the dreaded 'Endless Loop' will return.


Peter Kramer

I can understand the idea that adding an observer might not seem warranted in all cases, but think of the following. Whenever a transaction notice occurs - particularly a successful transaction, StoreKit deems the transaction to be incomplete. The application is expected to process the successful transaction notice, then notify the StoreKit that it has processed the transaction by making the finishTransaction call. This results in a URL notice to the StoreKit and on receipt, StoreKit marks the transaction as completed. This two way handshake is implemented as a means for StoreKit to know that a transaction has been received and processed by the app.


You might have seen the alert when a purchase attempt occurs - "You've already purchased this. Tap OK to download it again for free". This alert occurs because a previous attempt to purchase the item was successfully transacted on the StoreKit, but was left incomplete - for whatever reason, the URL command associated with the finishTransaction call, was not received by the StoreKit.


In this specific case, the app will see the successful transaction notice in the updatedTransactions delegate method and the app can issue the finishTransaction call to complete the transaction. In this case, its likely that the app had not called addTransactionObserver earlier. By calling addTransactionObserver early on, a check is made based on the current StoreKit user to see if there are incomplete transactions for the app to complete. If there are, the user is authenticated and the updatedTransactions delegate method called to report the succesful transaction.


Something else to consider. If there is an incomplete transaction and the restoreCompletedTransactions method is called to restore previous transactions, the incomplete transaction will not appear in the array of prodict identifiers to restore.


As to the recommendation to calling addTransactionObserver in the didFinishLaunchingWithOptions delegate method, this is a best practice item as noted in

Tech Note 2387 - “Best Practices for in app purchases”

<https://developer.apple.com/library/ios/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795>


You mention the "Endless Loop" issue - this sounds like an issue worthy of discussion. I haven't participated much on the forums, but I plan to do so more. If you could point me to a discussion on this issue, I can add my two cents.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

Hey! Thank you for that article, i think i will use your method of calling it in the didLoadWithOptions in appdelegate.

Sorry im new to in app purchases so your help is much appreciated. So do i just need to paste SKPaymentQueue.defaultQueue().addTransactionObserver(self) into the didLoadWithOptions func so it would look like this?


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

NSNotificationCenter.defaultCenter().postNotificationName("pauseGame", object: self)


SKPaymentQueue.defaultQueue().addTransactionObserver(self)


return true

}

Thank you again. 🙂

Rich, you wrote:


1) "You might have seen the alert when a purchase attempt occurs - 'You've already purchased this. Tap OK to download it again for free'......"

This alert will occur whenever a user with a new device requests to 'buy' an IAP rather than to 'restore' it. At this point in the dialog (i.e. the user is already at the App Store with a purchaseRequest) the user will have added the transaction observer whether they do it your way or mine so any unfinished transaction will already have come through to updatedTransactions.

2) "Something else to consider. If there is an incomplete transaction and the restoreCompletedTransactions method is called to restore previous transactions, the incomplete transaction will not appear in the array of prodict identifiers to restore." Using my approach of adding the transaction observer only when necessary the code will addTransactionObserver when it executes a restoreCompletedTransaction.


3) "As to the recommendation to calling addTransactionObserver in the didFinishLaunchingWithOptions delegate method, this is a best practice item as noted in

Tech Note 2387 - “Best Practices for in app purchases” " With all due respect, and IMHO, this "Best Practice" is not a "best practice" it's a "good practice" even though it is so listed. Or perhaps, my approach is a better practice then something called a "Best Practice". You know, in contract law words in capital letters can be defined to mean anything you wish and in Objective C you can define a term to mean anything for example:

NSString *BestPractice=@"an ok approach but not the best approach";

or

"an Exchange created by the State"


4) "You mention the "Endless Loop" issue - this sounds like an issue worthy of discussion." - many years ago the autorenewable subscription 'limit of 5' broke down and I got a few thousand renewals one weekend. Before I knew what had happened I deleted the test user. The transactions showed up every week like clockwork. Search the old forum or call Paul Marcos for more info. My point here is that an app is open to whatever might happen in StoreKit if it adds a transaction observer for no reason. It is not "Best Practice" or even "best practice" to add in extra Classes when they are most likely not needed - and that's what 'addTransactionObserver' must do. Keep in mind, you can't just addTransactionObserver, you have to add the delegate - unless you make the appDelegate the transaction observer delegate - and you are not suggesting that is best practice are you?


5) "I haven't participated much on the forums, but I plan to do so more." FANTASTIC. Thank you very much.

My response is here to item 5 - the endless loop. You comment - "many years ago the autorenewable subscription 'limit of 5' broke down and I got a few thousand renewals one weekend. Before I knew what had happened I deleted the test user. " - I've had similar reports and the course of action you took is similar in a manner to what I would suggest - "stop using that test user, create another and see if the problem persists. I don't have specific evidence, but I feel that a test user account can become "polluted" - for example when used in a condition where the finishTransaction process doesn't get called on successful transactions - leading to a state where incomplete transactions are associated with that user. It would be useful to see the associated console log generated by such app for presentation to iTunesConnect for their investigation.


The addTransactionObserver is there to notify the app of events generated by the App Store. If there is a problem with the delegate function being called more frequently than expected, the issue is with the App Store. I liken the transactionObserver to an eventProcessor from the App Store. If there are no transactions being submitted, there should be no events to process and the associated delegate method should never be called.


What would be useful is a profile which I have access to, but one which I've not been permitted to make available except as part of a DTS incident. Paul Marcos did work to attempt to get the profile published, but was unable to do so. I'll make another attempt at getting this done as the StoreKit profile provides a means to enable StoreKit logging for both development and production apps. With the log resulting from the profile, it becomes easier to see that the transactionObserver is simply the messenger. By calling removeTransactionObserver, you tell the messenger to stop bothering you. Actually, the mechanism is more like - telling the transactionObserver to stop asking the StoreKit whether there are more messages (StoreKit events) to process.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

I do not recommend ever deleting a test user. If there is a StoreKit transaction for a test user (e.g. an autorenewable that issues after deletion) then StoreKit will send a notice to the test user's device asking the device to log in with the test user. StoreKit will continue to send this notice (I believe once each week) until the test user logs in, the transaction is downloaded and finished. A deleted test user can't log in!

Just gonna add my two cents, NEVER delete a test user, i did and now i keep getting signin with errortesting@misshapen.com and it doesnt exsist anymore. All i have to do is press cancel and the newer version automatically pops up but it is annoying none the less