Swift / App Store : Problem with in app purchase - ios

I have a problem with my app. My in app purchase work very well on my side, on ios 12,4 iphone 5s. But when I send the binary at Apple Store I have a return :"We noticed that your app still still does not display the purchase button for the In-App Purchase product, in the app.".The buy button is only displayed when the processing to pay is ready. About 2 to 3 seconds at home. But with them apparently it does not work at all ...
Here is the full code for my store that handles everything. And I specify that with my iphone I manage to have the purchase window and to buy the integrated purchase in sandbox.
import UIKit
import StoreKit
import MessageUI
class ShopViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver, MFMailComposeViewControllerDelegate {
#IBOutlet weak var buyBtn: UIButton!
#IBOutlet weak var restore: UIButton!
#IBOutlet weak var mail: UIImageView!
#IBOutlet weak var shopDescription: UILabel!
var productsRequest = SKProductsRequest()
var validProducts = [SKProduct]()
var productIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
buyBtn.isHidden = true
shopDescription.numberOfLines = 0
shopDescription.lineBreakMode = NSLineBreakMode.byWordWrapping
shopDescription.sizeToFit()
shopDescription.text = NSLocalizedString("packpro", comment: "")
// SKPaymentQueue.default().add(self)
let tap4 = UITapGestureRecognizer(target: self, action:#selector(tappedMe5))
mail.addGestureRecognizer(tap4)
mail.isUserInteractionEnabled = true
fetchAvailableProducts()
}
func fetchAvailableProducts() {
let productIdentifiers = NSSet(objects:
"customLifePremium" // 0
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
validProducts = response.products
let prod100coins = response.products[0] as SKProduct
print("1st rpoduct: " + prod100coins.localizedDescription)
buyBtn.isHidden = false
}
}
/* func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
return true
}*/
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(_ product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
} else { print("Purchases are disabled in your device!") }
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
UserDefaults.standard.set(true, forKey: "premiumUser")
UserDefaults.standard.set(false, forKey: "limitedVersion")
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
print("Payment has failed.")
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
print("Purchase has been successfully restored!")
UserDefaults.standard.set(true, forKey: "premiumUser")
UserDefaults.standard.set(false, forKey: "limitedVersion")
break
default: break
}}}
}
func restorePurchase() {
SKPaymentQueue.default().add(self as SKPaymentTransactionObserver)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("The Payment was successfull!")
}
override func viewWillAppear(_ animated: Bool) {
setGradientBackground()
super.viewWillAppear(animated)
}
#IBAction func restoreCC(_ sender: Any) {
restorePurchase()
}
#IBAction func buyCC(_ sender: Any) {
productIndex = 0
purchaseMyProduct(validProducts[productIndex])
}
#objc func tappedMe5()
{
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients(["dfmv.enterprise#gmail.com"])
mail.setSubject("")
mail.setMessageBody("", isHTML: true)
present(mail, animated: true)
}else{
let alert = UIAlertController(title: NSLocalizedString("info", comment: ""), message: NSLocalizedString("noClientMail", comment: ""), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
func setGradientBackground() {
let colorTop = UIColor(red:1.00, green:0.30, blue:0.30, alpha:1.0).cgColor
let colorBottom = UIColor(red:1.00, green:0.69, blue:0.25, alpha:1.0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at:0)
}
}

Check this Gist that I personally used a week ago and passed the AppStore review for In-App Purchase flow.
IAP Service in Swift 5:
https://gist.github.com/egzonpllana/abd6d385bb45b1e329fe85a624ee531f
You can call IAPService.shared.getProducts() in AppDelegate method: didFinishLaunchingWithOptions to have all the products ready to use from your StoreKit in any viewcontroller, just check IAPService.shared.products.count.
Or call in your specific viewcontroller inside viewDidLoad and listen to changes with: IAPService.shared.didFinishRetrievingProducts = { [ weak self ] in ... }

Related

Where should I declare/instantiate my Store object to keep it available while app running?

I have an iOS app written in Swift where I have written code for the store in it's own class (Store). I then have a view controller (StoreController) which displays the product details and presents buttons so that the user can buy/restore purchases. I am using the delegate pattern.
My question is where should I create the Store object? Currently I'm declaring it as a property of StoreController which I think is wrong. I would prefer to be able to create it when my app loads and keep it until it quits.
The current problem is that if I test it and navigate to the StoreController view (initiating the request via Store) then press 'back' the app freezes. I assume because it's waiting on the response?
I saw one example online where someone created it in the AppDelegate file but this didn't work for me as I wasn't able to reference it from my StoreController.
This is my Store class:
import Foundation
import StoreKit
protocol ClassStoreDelegate: class {
func storeUpdateReceived(store: Store)
}
class Store: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
// Properties
weak var delegate: ClassStoreDelegate?
var list = [SKProduct]()
var p = SKProduct()
var defaults = UserDefaults.standard // Where we save whether the user is pro
var localTitle: String?
var localDescription: String?
var localPrice: String?
// Methods
// Calling the delegate method
func storeUpdate() {
delegate?.storeUpdateReceived(store: self)
}
// Buy the product
func buy() {
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.squidgylabs.pro") {
p = product
buyProduct()
}
}
}
// Restore products
func restore() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func getProducts() {
if(SKPaymentQueue.canMakePayments()) {
let productID: NSSet = NSSet(objects: "com.squidgylabs.pro")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
delegate?.storeUpdateReceived(store: self)
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let myProduct = response.products
for product in myProduct {
list.append(product)
}
// Update labels
localTitle = list[0].localizedTitle
localDescription = list[0].localizedDescription
// Format the price and display
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
if let formattedPrice = formatter.string(from: list[0].price){
localPrice = ("buy \(formattedPrice)")
delegate?.storeUpdateReceived(store: self)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
let transactionsArray = queue.transactions
if (transactionsArray.isEmpty) {
delegate?.storeUpdateReceived(store: self)
}
else {
for transaction in transactionsArray {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.squidgylabs.pro":
defaults.set(true, forKey: "pro")
delegate?.storeUpdateReceived(store: self)
default:
delegate?.storeUpdateReceived(store: self)
}
}
}
}
func buyProduct() {
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
switch trans.transactionState {
case .purchased:
let prodID = p.productIdentifier
switch prodID {
case "com.squidgylabs.pro":
defaults.set(true, forKey: "pro")
delegate?.storeUpdateReceived(store: self)
default:
delegate?.storeUpdateReceived(store: self)
}
queue.finishTransaction(trans)
break
case .failed:
delegate?.storeUpdateReceived(store: self)
queue.finishTransaction(trans)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
queue.finishTransaction(trans)
break
default:
break
}
}
}
}
And this is my StoreController (view controller):
import UIKit
class StoreController: UIViewController, ClassStoreDelegate {
// Properties
let store = Store()
// Outlets
#IBOutlet weak var localTitle: UILabel!
#IBOutlet weak var localDescription: UILabel!
#IBOutlet weak var buy: UIButton!
#IBOutlet weak var restore: UIButton!
// Actions
#IBAction func buy(_ sender: UIButton) {
print("buy pressed")
store.buy()
}
#IBAction func restore(_ sender: UIButton) {
print("restore pressed")
store.restore()
}
// Methods
override func viewDidLoad() {
super.viewDidLoad()
store.delegate = self // bind the delegate like this?
// Get list of products for the store
localTitle.isEnabled = false
localDescription.isEnabled = false
buy.isEnabled = false
restore.isEnabled = false
self.navigationItem.title = "Store"
// update once the list of products is got from the store object
store.getProducts()
}
// Running the delegate update
func storeUpdateReceived(store: Store) {
print("storeUpdateReceived activated")
if ((store.localTitle) != nil) {
localTitle.text = store.localTitle!
localDescription.text = store.localDescription
buy.setTitle(store.localPrice, for: .normal)
localTitle.isEnabled = true
localDescription.isEnabled = true
buy.isEnabled = true
restore.isEnabled = true
}
}
}
You're right - instantiate it in your AppDelegate. You know it will be called once and only once, so it's a good place to initialise things.
You can access the AppDelegate from anywhere in your app
appDelegate = (UIApplication.shared.delegate as! AppDelegate)

Redeem Button for in app purchases

I submitted my app to apple but they rejected it because, there was no redeem button for in app purchases. How can i do that. I made a redeem button and linked it up to the view controller. Where do i go from there?
Here is my code:
class StoreViewController: UIViewController, SKPaymentTransactionObserver, SKProductsRequestDelegate {
#IBOutlet var redeemOutlet: UIButton!
#IBAction func redeem(_ sender: Any) {
}
#IBOutlet var productDescription: UITextView!
var product: SKProduct?
let productID = "com.myCoolAwesomeApp.mjay.noAdvertisements"
#IBOutlet var buyButton: UIButton!
#IBOutlet var returnButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
buyButton.layer.cornerRadius = 5.0
returnButton.layer.cornerRadius = 5.0
buyButton.isEnabled = false
SKPaymentQueue.default().add(self)
getPurchaseInfo()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func purchase(_ sender: Any) {
let payment = SKPayment(product: product!)
SKPaymentQueue.default().add(payment)
}
#IBAction func dismissView(_ sender: Any) {
//self.dismiss(animated: true, completion: nil)
}
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 = "Remove all advertisements within the game for $0.99"
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
}
}
}
}

Why is my array out of bounds?

I am trying to incorporate an in-app purchase. For some reason I am getting a fatal error that states 'fatal error: Index out of range' with an array that holds the SKProduct products. I have one nonconsumable in app purchase, but may decide to add more later. I am trying to access the PREMIUM_PRODUCT_ID product so I can make a purchase. The error occurs when the purchaseMyProduct function is called. Any ideas in why the iapProducts array is out of bounds? Thanks for your help!
The fatal error occurs on this line purchaseMyProduct(product: iapProducts[0])
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
let PREMIUM_PRODUCT_ID = "---------------"
var productID = ""
var productsRequest = SKProductsRequest()
var iapProducts = [SKProduct]()
var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade")
#IBOutlet weak var adsBtn: UIButton!
#IBAction func restorePurchase(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
// Check your In-App Purchases
print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)")
// Fetch IAP Products available
fetchAvailableProducts()
UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()
}
#IBAction func review(_ sender: Any) {
UIApplication.shared.openURL(NSURL(string: "-----------------")! as URL)
}
#IBAction func removeAds(_ sender: Any) {
//UIApplication.shared.openURL(NSURL(string: "----------------")! as URL)
purchaseMyProduct(product: iapProducts[0])
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
...
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
/*UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()*/
}
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
PREMIUM_PRODUCT_ID
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
// 1st IAP Product (Consumable) ------------------------------------
let firstProduct = response.products[0] as SKProduct
// Get its price from iTunes Connect
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
numberFormatter.locale = firstProduct.priceLocale
let price1Str = numberFormatter.string(from: firstProduct.price)
// Show its description
//consumableLabel.text = firstProduct.localizedDescription + "\nfor just \(price1Str!)"
// ------------------------------------------------
// 2nd IAP Product (Non-Consumable) ------------------------------
let secondProd = response.products[0] as SKProduct
// Get its price from iTunes Connect
numberFormatter.locale = secondProd.priceLocale
let price2Str = numberFormatter.string(from: secondProd.price)
// Show its description
//nonConsumableLabel.text = secondProd.localizedDescription + "\nfor just \(price2Str!)"
// ------------------------------------
}
}
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
// IAP Purchases dsabled on the Device
} else {
UIAlertView(title: "IAP Tutorial",
message: "Purchases are disabled in your device!",
delegate: nil, cancelButtonTitle: "OK").show()
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// The Consumable product (10 coins) has been purchased -> gain 10 extra coins!
if productID == PREMIUM_PRODUCT_ID {
// Save your purchase locally (needed only for Non-Consumable IAP)
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
//premiumLabel.text = "Premium version PURCHASED!"
UIAlertView(title: "IAP Tutorial",
message: "You've successfully unlocked the Premium version!",
delegate: nil,
cancelButtonTitle: "OK").show()
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}}}
}
}
Your code is way too long, so I took the liberty of making an educated guess what is relevant:
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
var iapProducts = [SKProduct]()
#IBAction func removeAds(_ sender: Any) {
purchaseMyProduct(product: iapProducts[0])
}
}
When you declare iapProducts, you are creating an empty array that contains no values.
Then, in your removeAds function, you are telling it to do something with the first element.
I am going to take a wild guess that nothing exists, so you are forcing it to access an object that doesn't exist.
Try the following and see if it fixes your issue.
var iapProducts: [SKProduct]?
#IBAction func removeAds(_ sender: Any) {
guard let product = iapProducts?.first else { return }
purchaseMyProduct(product: product)
}
This accomplishes two things:
It makes your [SKProduct] array an optional
The guard statement safely unwraps the first product, so only if one exists, will it try to call your purchaseMyProduct function. Otherwise, it just exits

Type "GameScene" does not conform to protocol "SKPaymentTransactionObserver

Ok so I was following a tutorial (http://stefansdevplayground.blogspot.com/2015/04/how-to-implement-in-app-purchase-for.html) and when I finished following it, I had the error:
Type "GameScene" does not conform to protocol "SKPaymentTransactionObserver".
I'm a beginner and haven't used storekit before but some help would be nice.
(By the way, I haven't updated the NSUserDefaults and names he made in the tutorial yet to my game)
class RemoveAdsScene: SKScene, SKPaymentTransactionObserver, SKProductsRequestDelegate {
private var request : SKProductsRequest!
private var products : [SKProduct] = [] // List of available purchases
private var greenShipPurchased = false // Used to enable/disable the 'green ship'
let settingsTitle = SKLabelNode(text: "[REMOVE|ADS]")
let infoLabel = SKLabelNode(text: "Pay What You Want")
let infoLabel2 = SKLabelNode(text: "To Remove Ads!")
let button199 = SKSpriteNode(imageNamed: "199Button")
let backButton = SKSpriteNode(imageNamed: "BackButton")
var audioPlayer: AVAudioPlayer!
override func didMoveToView(view: SKView) {
infoLabel.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.825)
infoLabel.zPosition = 500
infoLabel.fontSize = 25
infoLabel.fontName = "Montserrat-Bold"
infoLabel.fontColor = SKColor.blackColor()
self.addChild(infoLabel)
infoLabel2.position = CGPointMake(self.frame.size.width/2, self.frame.size.height*0.775)
infoLabel2.zPosition = 500
infoLabel2.fontSize = 25
infoLabel2.fontName = "Montserrat-Bold"
infoLabel2.fontColor = SKColor.blackColor()
self.addChild(infoLabel2)
settingsTitle.fontColor = UIColor.init(red: 0.902, green: 0.251, blue: 0.282, alpha: 1)
settingsTitle.fontSize = 85
settingsTitle.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.9)
settingsTitle.fontName = "KGDefyingGravityBounce"
self.addChild(settingsTitle)
button199.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.7)
button199.zPosition = 15
self.addChild(button199)
backButton.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height * 0.3)
backButton.zPosition = 15
self.addChild(backButton)
if NSUserDefaults.standardUserDefaults().objectForKey("timeOfTheDay") as! String == "morning" {
backgroundColor = GlobalData.dayColor
infoLabel.fontColor = SKColor.blackColor()
infoLabel2.fontColor = SKColor.blackColor()
}
else {
backgroundColor = GlobalData.nightColor
infoLabel.fontColor = SKColor.whiteColor()
infoLabel2.fontColor = SKColor.whiteColor()
}
}
func inAppPurchase() {
let alert = UIAlertController(title: "In App Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
// Add an alert action for each available product
for (var i = 0; i < products.count; i += 1) {
let currentProduct = products[i]
if !(currentProduct.productIdentifier == "MySecondGameGreenShip" && greenShipPurchased) {
// Get the localized price
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = currentProduct.priceLocale
// Add the alert action
alert.addAction(UIAlertAction(title: currentProduct.localizedTitle + " " + numberFormatter.stringFromNumber(currentProduct.price)!, style: UIAlertActionStyle.Default) { _ in
// Perform the purchase
self.buyProduct(currentProduct)
})
}
}
// Offer the restore option only if purchase info is not available
if(greenShipPurchased == false) {
alert.addAction(UIAlertAction(title: "Restore", style: UIAlertActionStyle.Default) { _ in
self.restorePurchasedProducts()
})
}
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
// Initialize the App Purchases
func initInAppPurchases() {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
// Get the list of possible purchases
if self.request == nil {
self.request = SKProductsRequest(productIdentifiers: Set(["MySecondGameGreenShip","MySecondGameDonate"]))
self.request.delegate = self
self.request.start()
}
}
func buyProduct(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
func restorePurchasedProducts() {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
self.products = response.products
self.request = nil
}
func request(request: SKRequest, didFailWithError error: NSError) {
print(error)
self.request = nil
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch (transaction.transactionState) {
case .Purchased:
if transaction.payment.productIdentifier == "MySecondGameGreenShip" {
handleGreenShipPurchased()
}
queue.finishTransaction(transaction)
case .Restored:
if transaction.payment.productIdentifier == "MySecondGameGreenShip" {
handleGreenShipPurchased()
}
queue.finishTransaction(transaction)
case .Failed:
print("Payment Error: %#", transaction.error)
queue.finishTransaction(transaction)
default:
print("Transaction State: %#", transaction.transactionState)
}
}
}
func handleGreenShipPurchased() {
greenShipPurchased = true
checkAndActivateGreenShip()
// persist the purchase locally
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "MySecondGameGreenShip")
}
func checkAndActivateGreenShip() {
if NSUserDefaults.standardUserDefaults().boolForKey("MySecondGameGreenShip") {
greenShipPurchased = true
}
}
Whenever you feel like you conform to a protocol but Xcode tells you you don't it is a wise idea to look at the protocol documentation to see where you might be wrong. In this case the protocol SKPaymentTransactionObserver has one required method:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction])
you try to implement that method but fail since your code does not match the function definition. You wrote
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
To fix it simply take the correct function head and slightly alter your following code:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
...
}

In-App Purchase Swift

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

Resources