Paul Satem Paul Satem - 7 months ago 35
Swift Question

In-App Purchase & Restore Button : Single Product - Non-Consumable

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:


  1. Allows the user to make an in-app purchase to remove all ads (AdBanners & InterstitialAds); or

  2. 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:


  1. Is my above code correct?

  2. What should I modify and why?



Apologies in advance, I am new to this wonderful world of coding ... but loving every minute of it!

Answer

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.