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
Related
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.
I'm trying to implement In App Purchase in iOS using Swift, but the Restore Purchase functionality is giving problem. Whenever I click on the restore purchase button it is working fine but it keeps adding it to the queue. Please Help.
Below is my code and output.
In the output "IAP not setup method3" keeps on increasing by one whenever I click on Restore Purchase button.
import UIKit
import StoreKit
class PremiumUpgradeVC: UIViewController , SKProductsRequestDelegate, SKPaymentTransactionObserver{
#IBOutlet weak var uiScrollVw: UIScrollView!
#IBOutlet weak var btnPremiumPurchase: UIButton!
#IBOutlet weak var btnPrice: UIButton!
#IBOutlet weak var btnRestorePur: UIButton!
var removeAds : Bool = false
var enableCompilations : Bool = false
var premiumPurchasedKey : String = "isPremiumPurchased"
var list = [SKProduct]()
var p = SKProduct()
var IAPId = "com.creativecub.testme"
override func viewDidLoad() {
super.viewDidLoad()
if(SKPaymentQueue.canMakePayments()){
print("IAP is enabled, loading")
var productID:NSSet = NSSet(objects: IAPId)
var request :SKProductsRequest = SKProductsRequest(productIdentifiers : productID as! Set<String>)
request.delegate = self
request.start()
//loadingAlert.
}else{
print("please enable IAP")
}
}
#IBAction func actionGoPremium(sender: UIButton) {
print("on action button")
for product in list{
var prodID = product.productIdentifier
if(prodID == IAPId){
p = product
buyProduct()
break;
}
}
}
func goPremi(){
print("in method : gopremium")
// removeAds = true
// enableCompilations = true
HelperClass.setValSharedPerf(premiumPurchasedKey, value: "true")
print("ads removed and compilations enables .. Enjoy")
}
#IBAction func actionRestorePurchase(sender: UIButton) {
print("in method : 1")
SKPaymentQueue.defaultQueue().addTransactionObserver(self) SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
func buyProduct() {
print("buy product -" + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
print("in method : 2")
}
//restored transaction function
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("in method : 3")
//print("transaction retsored")
var purchasedItemIDs = []
for transaction in queue.transactions{
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case IAPId:
print("premium Upgrade method 3")
goPremi()
SKPaymentQueue.defaultQueue().finishTransaction(transaction as SKPaymentTransaction)
default:
print("IAP not setup method3") // this keeps increasing
}
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("in method : 4")
print("product request")
var myProducts = response.products
for product in myProducts {
print("product added - details :" + product.productIdentifier + product.localizedTitle)
let price = priceStringForProduct(product)
btnPrice.setTitle("Buy for: "+price!, forState: UIControlState.Normal)
list.append(product)
}
}
//restore purchase
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
// print("added payment")
print("in method : 5")
for transaction:AnyObject in transactions{
let trans = transaction as! SKPaymentTransaction
//print(trans.error)
switch trans.transactionState {
case .Purchased: //switch1 case 1
print("buy ok, unlock IAP here")
let prodID = p.productIdentifier as String
switch prodID {
case IAPId:
print("premium Upgrade .. purchase restored")
goPremi()
queue.finishTransaction(trans)
default:
print("IAP not setup method5")
}
queue.finishTransaction(trans)
break;
case .Failed: //switch1 case 2
print("error in buy")
queue.finishTransaction(trans)
break;
default: //switch1 case 3
print("default case method 5")
break;
}
}
}
func finishTransaction(trans:SKPaymentTransaction){
print("finsh trans")
SKPaymentQueue.defaultQueue().finishTransaction(trans)
print("in method : 6")
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("remove trnas")
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
print("in method : 7")
}
func priceStringForProduct(item: SKProduct) -> String? {
let numberFormatter = NSNumberFormatter()
let price = item.price
let locale = item.priceLocale
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = locale
return numberFormatter.stringFromNumber(price)
}
func goBack()
{
self.dismissViewControllerAnimated(true, completion: nil);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
print("disapeard")
}
}
This is the output:
in method : 1
in method : 5
default case method 5
default case method 5
in method : 3
IAP not setup method3 //this statement keep increasing on button click
IAP not setup method3
IAP not setup method3
IAP not setup method3
IAP not setup method3
premium Upgrade method 3
in method : gopremium
ads removed and compilations enables .. Enjoy
remove trnas
in method : 7
You should not do anything in paymentQueueRestoreCompletedTransactionsFinished aside from updating your UI if required. This delegate method is called to indicate that the restoration process is complete; nothing more.
The restored transactions will be delivered to your updated transactions method with a state of restored. You should process them the same as you do with the state of purchased
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.
This issue has had me going for days.
I have a simple app, that displays banners and interstitial adverts.
I am using a single view application, have main view controller (ViewController.swift) and have set up another view controller (InAppViewController.swift) to handle a pop-up page that:
Allows the user to make an in-app purchase to remove all ads (AdBanners & InterstitialAds); or
Restore purchases.
My code is error-free when I run it.
In-app purchases are running ok, but occasionally I get a sign-in request to iTunes twice.
But my restore button and associated functionality seem to be the problem.
I have set-up numerous sandbox tester accounts to test, and a new user that has not bought the app is able to restore purchases successfully. Which should not be possible, so I definitely have done something wrong here.
Here is my code:
Main View Controller:
// ViewController.swift
import UIKit
import MessageUI
import Social
import iAd
import StoreKit
class ViewController: UIViewController, MFMailComposeViewControllerDelegate, MFMessageComposeViewControllerDelegate, ADBannerViewDelegate, ADInterstitialAdDelegate
{
let defaults = NSUserDefaults.standardUserDefaults()
var product_id: NSString?;
override func viewDidLoad() {
product_id = "some.product.id";
super.viewDidLoad()
//Check if product is purchased
if (defaults.boolForKey("purchased")){
print("already purchased")
// Hide or show banner ads is purchased/not purchased.
// Advertising Banner:
self.canDisplayBannerAds = false
}
else if (!defaults.boolForKey("stonerPurchased")){
print("not yet purchased")
// Advertising Banner:
self.canDisplayBannerAds = true
}
This code seems to work perfectly. When the app loads, it is able to determine who has paid to remove ads and those who have not paid, and ad banners are shown appropriately.
It is in the second view controller (InAppPViewController.swift) That I am having problems.
here is my code:
Second View Controller - InAppViewController.swift:
// InAppPViewController.swift
import UIKit
import StoreKit
import iAd
class InAppPViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
let defaults = NSUserDefaults.standardUserDefaults()
var product_id: NSString?;
#IBOutlet weak var unlockAction: UIButton!
#IBOutlet var adBannerView: ADBannerView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func restorePurchases(sender: UIButton) {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("Transactions Restored")
let alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
#IBAction func unlockAction(sender: AnyObject) {
product_id = "some.product.id";
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//Check if product is purchased
if (defaults.boolForKey("purchased")){
}
else if (!defaults.boolForKey("stonerPurchased")){
print("false")
}
print("About to fetch the products");
// We check that we are allowed to make the purchase.
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fething Products");
}else{
print("can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
//Delegate Methods for IAP
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
buyProduct(validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
func request(request: SKRequest, didFailWithError error: NSError) {
print("Error Fetching product information");
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
print("Product Purchased");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
defaults.setBool(true , forKey: "purchased")
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Already Purchased");
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
default:
break;
}
}
}
}
}
Where am I going wrong?
Questions:
Is my above code correct?
What should I modify and why?
Apologies in advance, I am new to this wonderful world of coding ... but loving every minute of it!
SKPaymentQueue.defaultQueue().addTransactionObserver(self) should be in viewDidLoad() not in restore func
You can put SKPaymentQueue.defaultQueue().restoreCompletedTransactions() like this
override func viewWillDisappear(animated: Bool) {
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
}
login twice in sandbox is normal.
hope I helped with something.
I have amended my code for the InAppPViewController.swift file as follows:
// InAppPViewController.swift
import UIKit
import StoreKit
class InAppPViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
let defaults = NSUserDefaults.standardUserDefaults()
var product_id: NSString?;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func restorePurchases(sender: UIButton) {
// Set up the observer
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//Check if user can make payments and then proceed to restore purchase
if (SKPaymentQueue.canMakePayments()) {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
}
#IBAction func unlockAction(sender: AnyObject) {
product_id = "some.product.id";
// Adding the observer
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//Check if product is purchased
if (defaults.boolForKey("purchased")){
print("User already purchased this")
// Hide a view or show content depends on your requirement
}
else if (!defaults.boolForKey("Purchased")){
print("User has not yet pur hased this")
}
print("About to fetch the products");
// Check if user can make payments and then proceed to make the purchase.
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("User can make purchases and will fetch products from Apple Store now");
}else{
print("User can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
buyProduct(validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
func request(request: SKRequest, didFailWithError error: NSError) {
print("Error Fetching product information");
}
// Allowing for all possible outcomes:
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .Purchased:
print("Product Purchased")
let alert = UIAlertView(title: "Thank You", message: "Thank you for your purchase!", delegate: nil, cancelButtonTitle: "OK")
alert.show();
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
defaults.setBool(true , forKey: "purchased")
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Already Purchased");
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
break;
default:
break;
}
}
}
}
}
I left the ViewController.swift file as is.
Product purchases seem to work now.
But regarding Restore Purchases, I can run the code on my physical device, but cannot test the Restore Purchases function.
I am caught with previous Restore Purchases that are still unresolved and looping in the system. I am unable to clear my SKPaymentsQueue manually. Thus my code refuses to entertain anymore new Restore Purchase requests.
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.