Restore Button for iOS Non-Consumable In-App Purchase

Consumable In-App Purchase and Restore Button

Consumables are not to be restored, just as you were reasoning in your original question.

In app purchase restore button doesn't cause any action?

Non-consumable purchases can only be purchased once, so this is the expected behavior. The message is displayed because you can only purchase the non-consumable item once, and it remains associated with the account. It thinks that since you have purchased the item already, that you simply want to restore it. As a side note, for testing purposes, IAPs have to be tested using a real device, so the simulator won't test IAPs correctly.

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

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.

Restore Purchase : Non-Consumable

Your codes looks pretty fine for the most part, although some parts seem to be from older tutorials . There is some changes you should make, one of them is that you need to call your unlockProduct function again.

This is the code I use (Swift 3).

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

for transaction in transactions {
switch transaction.transactionState {

case .purchasing:
// Transaction is being added to the server queue.

case .purchased:
// Transaction is in queue, user has been charged. Client should complete the transaction.

defer {
queue.finishTransaction(transaction)
}

let productIdentifier = transaction.payment.productIdentifier

unlockProduct(withIdentifier: productIdentifier)

case .failed:
// Transaction was cancelled or failed before being added to the server queue.

defer {
queue.finishTransaction(transaction)
}

let errorCode = (transaction.error as? SKError)?.code

if errorCode == .paymentCancelled {
print("Transaction failed - user cancelled payment")
} else if errorCode == .paymentNotAllowed { // Will show alert automatically
print("Transaction failed - payments are not allowed")
} else {
print("Transaction failed - other error")
// Show alert with localised error description
}

case .restored:
// Transaction was restored from user's purchase history. Client should complete the transaction.

defer {
queue.finishTransaction(transaction)
}

if let productIdentifier = transaction.original?.payment.productIdentifier {
unlockProduct(withIdentifier: productIdentifier)
}

case .deferred:
// The transaction is in the queue, but its final status is pending external action
// e.g family member approval (FamilySharing).
// DO NOT freeze up app. Treate as if transaction has not started yet.
}
}
}

Than use the delegate methods to show the restore alert

/// Restore finished
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
guard queue.transactions.count != 0 else {
// showAlert that nothing restored
return
}

// show restore successful alert
}

/// Restore failed
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {

/// handle the restore error if you need to.
}

Unlock product is just a method I am sure you already have too.

  func unlockProduct(withIdentifier productIdentifier: String) {
switch productIdentifier {
/// unlock product for correct ID
}
}

As a side note, you should move this line

 SKPaymentQueue.default().add(self)

out of your restore and buy function and put it in viewDidLoad.

Apple recommends you add the transaction observer as soon as your app launches and only remove it when your app is closed. A lot of tutorials unfortunately dont teach you this correctly. This way you unsure that any incomplete transactions e.g due to network error, will always resume correctly.

https://developer.apple.com/library/content/technotes/tn2387/_index.html

In my real projects my code for IAPs is in a Singleton class so I would actually using delegation to forward the unlockProduct method to my class that handles gameData. I can than also make sure the observer is added at app launch.

Hope this helps

What should happen when non-purchased user press the restore purchase button in iOS?

You can check if the queue has any returned transactions, and if not it means that there are no purchases to restore:

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
if queue.transactions.count == 0 {
let alert = UIAlertView()
alert.title = "Oops"
alert.message = "There are no purchases to restore, please buy one"
alert.addButtonWithTitle("Buy")
alert.addButtonWithTitle("Cancel")
alert.show()
}
}

No restore button for in app purchase causes rejection

Use the following to restore the products ID's that user did purchased from your app

- (void) checkPurchasedItems
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}// Call This Function

//Then this delegate Function Will be fired
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
purchasedItemIDs = [[NSMutableArray alloc] init];

NSLog(@"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSString *productID = transaction.payment.productIdentifier;
[purchasedItemIDs addObject:productID];
}

}

the purchasedItemIDs will contain all the product IDs that the user purchased it .. you could put a button to call this function when it finished you show all these products to enable the user to download it again.



Related Topics



Leave a reply



Submit